﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using Fuzzware.Common;
using Fuzzware.Common.XML;
using Fuzzware.Common.XML.Restrictions;
using Fuzzware.Common.Interop;
using Fuzzware.Common.MethodInterface;
using Fuzzware.Convert2XML;
using Fuzzware.Schemas.AutoGenerated;

namespace Fuzzware.Evaluate
{
    class OutputToCOMHandler : OutputToExeHandler
    {
        OutputToCOM oOutputToCOM;
        COMInputHandler oCOMInputHandler;

        // Output to Script
        OutputToCOMEvaluateInScript oScriptSettings;
        String ScriptTemplate;
        const String constDefaultScriptTemplate = "ActiveXInvoke.htm";
        const String constCLASSID = "%CLASSID%";
        const String constPROGID = "%PROGID%";
        const String constSCRIPT = "%SCRIPT%";
        const String constFormatOpFn = "__opString";        // Function to format an operation string for output
        const String constFormatErrorFn = "__errorString";  // Function to format an error string for output
        const String constOutputFn = "__output";            // Function to output a message
        const String constOutputLnFn = "__outputLn";        // Function to output a message with a line break
        const String constOBJ = "obj";
        SortedList<XmlQualifiedName, String> oInterfaceVariables;
        SortedList<XmlQualifiedName, XmlQualifiedName> oNEInterfaceVariables;   // Non-existant interface variables
        SortedList<XmlQualifiedName, XmlQualifiedName> oUncallableMethods;      // List of methods that are unable to be called

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern SafeFileHandle CreateFile(string lpFileName,
            FileAccess dwDesiredAccess, FileShare dwShareMode,
            IntPtr lpSecurityAttributes, FileMode dwCreationDisposition,
            int dwFlagsAndAttributes, IntPtr hTemplateFile);

        public override void Initialise(object Settings, Fuzzware.Convert2XML.InputHandler oInputHandler)
        {
            if (!(Settings is OutputToCOM))
                Log.Write(MethodInfo.GetCurrentMethod(), "Expected Settings object of type 'OutputToCOM', got '" + Settings.GetType().ToString() + "'", Log.LogType.Error);

            oOutputToCOM = Settings as OutputToCOM;

            if (!(oInputHandler is COMInputHandler))
                Log.Write(MethodInfo.GetCurrentMethod(), "To use 'OutputToCOM', the input must come from 'COMInput'", Log.LogType.Error);

            oCOMInputHandler = (oInputHandler as COMInputHandler);

            // Check whether we are invoking directly
            if (oOutputToCOM.Item is OutputToCOMEvaluateAsStandAlone)
            {
                Log.Write(MethodBase.GetCurrentMethod(), "EvaluateAsStandAlone is currently not supported", Log.LogType.Error);
            }
            // Check if we are invoking in browser
            else if (oOutputToCOM.Item is OutputToCOMEvaluateInScript)
            {
                oScriptSettings = oOutputToCOM.Item as OutputToCOMEvaluateInScript;
                // Validate the script template to execute
                if (String.IsNullOrEmpty(oScriptSettings.ActiveXScriptTemplate))
                {
                    // Use the default
#if DEBUG
                    String ResourcesRelativePath = @"..\..\..\Resources";
#else
                    String ResourcesRelativePath = @"Resources";
#endif
                    oScriptSettings.ActiveXScriptTemplate = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), ResourcesRelativePath + Path.DirectorySeparatorChar + constDefaultScriptTemplate));
                }
                if (!File.Exists(Path.GetFullPath(oScriptSettings.ActiveXScriptTemplate)))
                {
                    Log.Write(MethodBase.GetCurrentMethod(), "Script template file '" +
                        Path.GetFullPath(oScriptSettings.ActiveXScriptTemplate) + "' could not be found", Log.LogType.Error);
                }
                Log.Write(MethodBase.GetCurrentMethod(), "Using ActiveX script template file '" + Path.GetFullPath(oScriptSettings.ActiveXScriptTemplate) + "'", Log.LogType.Info);
                // Read in the script template to execute
                using (FileStream fs = new FileStream(Path.GetFullPath(oScriptSettings.ActiveXScriptTemplate), FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    StreamReader sr = new StreamReader(fs);
                    ScriptTemplate = sr.ReadToEnd();
                }

                if(String.IsNullOrEmpty(ScriptTemplate))
                    Log.Write(MethodBase.GetCurrentMethod(), "Contents of '" + 
                        Path.GetFullPath(oScriptSettings.ActiveXScriptTemplate) + "' were empty", Log.LogType.Error);

                // Configure the OutputToExe options
                OutputToExe oExeConfig = oScriptSettings.OutputToExe;
                // The input and output file settings
                if(String.IsNullOrEmpty(oExeConfig.InputFromFile.Directory))
                    oExeConfig.InputFromFile.Directory = oExeConfig.UniqueOutputs.Directory;
                if(String.IsNullOrEmpty(oExeConfig.InputFromFile.FileExtension))
                    oExeConfig.InputFromFile.FileExtension = Path.GetExtension(oScriptSettings.ActiveXScriptTemplate);
                if(String.IsNullOrEmpty(oExeConfig.UniqueOutputs.FileExtension))
                    oExeConfig.UniqueOutputs.FileExtension = ".txt";
                base.Initialise(oExeConfig, null);
            }
        }

        public override bool Output(System.IO.MemoryStream XMLMemoryStream, string StateDesc)
        {
            // Load the XML into a XmlDocument
            XmlDocument oXMLDoc = new XmlDocument();
            oXMLDoc.Load(XMLMemoryStream);
            XPathNavigator oXPathNav = XMLHelper.GetRootNode(oXMLDoc);

            // Load the interface
            LibraryNode oLibraryNode = new LibraryNode(oCOMInputHandler.Description);
            oLibraryNode.Deserialise(oXPathNav);

            // Check whether we are invoking directly
            if (oOutputToCOM.Item is OutputToCOMEvaluateAsStandAlone)
            {
                //Type COMType = Type.GetTypeFromCLSID(oCOMInputHandler.ClassID);

                //object COMObject = Activator.CreateInstance(COMType);

                //MethodInfo[] oMethods = COMType.GetMethods();
            }
            // Check if we are invoking in IE
            else if (oOutputToCOM.Item is OutputToCOMEvaluateInScript)
            {
                return OutputToScript(oLibraryNode, StateDesc);
            }

            return true;
        }

        /// <summary>
        /// Invoke COM object in a browser
        /// </summary>
        private bool OutputToScript(LibraryNode oLibraryNode, String StateDesc)
        {
            if (Guid.Empty == oCOMInputHandler.ClassID)
                Log.Write(MethodBase.GetCurrentMethod(), "CLSID is '" + Guid.Empty.ToString() + "'", Log.LogType.Error); 
            // Replace the CLASSID
            String TemplateOutput = ScriptTemplate.Replace(constCLASSID, oCOMInputHandler.ClassID.ToString());
            // Replace the PROGID
            if(!String.IsNullOrEmpty(oCOMInputHandler.ProgID))
                TemplateOutput = TemplateOutput.Replace(constPROGID, oCOMInputHandler.ProgID);
            
            // Generate the Script
            String Script = GenerateScript(oLibraryNode);

            // Replace the script
            TemplateOutput = TemplateOutput.Replace(constSCRIPT, Script);

            // Write script to a MemoryStream
            MemoryStream oScriptStream = new MemoryStream();
            byte[] ScriptBytes = Encoding.Unicode.GetBytes(TemplateOutput);
            oScriptStream.Write(ScriptBytes, 0, ScriptBytes.Length);
            oScriptStream.Seek(0, SeekOrigin.Begin);

            // Invoke our exe
            return base.Output(oScriptStream, StateDesc);
        }

        /// <summary>
        /// Create a file stream, we have to use CreateStream directly as System.IO does not let us create file streams
        /// </summary>
        private static FileStream CreateFileStream(string path, FileAccess access, FileMode mode, FileShare share)
        {
            if (mode == FileMode.Append) 
                mode = FileMode.OpenOrCreate;
            
            SafeFileHandle handle = CreateFile(path, FileAccess.Write, share, IntPtr.Zero, mode, 0, IntPtr.Zero);
            if (handle.IsInvalid)
            {
                Log.Write(MethodBase.GetCurrentMethod(), "Could not create file stream '" + path + "'", Log.LogType.Warning);
                return null;
            }
            return new FileStream(handle, access);
        }

        /// <summary>
        /// We write the file but then add an Alternate Data Stream to indicate the file came from the Intranet Zone, this
        /// means IE will play it without warning (for typical configurations).
        /// </summary>
        protected override bool WriteToFile(MemoryStream XMLMemoryStream, string InputPathAndFile, string StateDesc)
        {
            if (!base.WriteToFile(XMLMemoryStream, InputPathAndFile, StateDesc))
                return false;

            // Add Zone.Identifier Alternate Data Stream to file
            String ZoneIdADSName = Path.GetFullPath(InputPathAndFile) + ":Zone.Identifier";
            FileStream ADSStream = CreateFileStream(ZoneIdADSName, FileAccess.Write, FileMode.Create, FileShare.None);
            if (null == ADSStream)
                return false;
            // Write ADS text
            // [ZoneTransfer]
            // ZoneId=1
            String ADSText = "[ZoneTransfer]" + Environment.NewLine + "ZoneId=1";
            byte[] ADSBytes = Encoding.Unicode.GetBytes(ADSText);
            ADSStream.Write(ADSBytes, 0, ADSBytes.Length);
            ADSStream.Flush();
            ADSStream.Close();
            return true;
        }

        /// <summary>
        /// Generate script that calls the methods of the interface
        /// </summary>
        private String GenerateScript(LibraryNode oLibraryNode)
        {
            SortedList<XmlQualifiedName, string> oOutputVars = new SortedList<XmlQualifiedName, string>(new XMLHelper.XmlQualifiedNameComparer());
            StringBuilder Script = new StringBuilder();

            // We keep track of interfaces we cannot create.  We will always be able to create the target interface 'obj'.
            if(null == oNEInterfaceVariables)
                oNEInterfaceVariables = new SortedList<XmlQualifiedName,XmlQualifiedName>(new XMLHelper.XmlQualifiedNameComparer());
            // Keep track of methods that cannot be called.  Do this so when fuzzing we are not bombarded with messages about these methods.
            if (null == oUncallableMethods)
                oUncallableMethods = new SortedList<XmlQualifiedName, XmlQualifiedName>(new XMLHelper.XmlQualifiedNameComparer());

            // Loop through each method and call it
            for (int i = 0; i < oLibraryNode.InterfaceNodes.Count; i++)
            {
                // Get the interface name
                XmlQualifiedName InterfaceName = new XmlQualifiedName(oLibraryNode.InterfaceNodes[i].Description.Name, oLibraryNode.InterfaceNodes[i].Description.Namespace);
                // Check if we have already failed to get this interface
                if (oNEInterfaceVariables.ContainsKey(InterfaceName))
                    continue;
                // Get a variable for this interface
                List<String> IntMethodStack = new List<string>();
                String ObjVarName = GetVariableForType(oLibraryNode, InterfaceName, IntMethodStack, oOutputVars, oLibraryNode.Description.Interfaces.Count + 1, false);
                if (String.IsNullOrEmpty(ObjVarName))
                {
                    // We failed to get a variable for the interface, record this so we don't try again
                    oNEInterfaceVariables.Add(InterfaceName, InterfaceName);
                    Log.Write(MethodBase.GetCurrentMethod(), "Unable to get interface variable for '" + oLibraryNode.InterfaceNodes[i].Description.Name + "'.  Ignoring methods of this interface", Log.LogType.Warning);
                    continue;
                }
                if (IntMethodStack.Count > 0)
                {
                    // The interface variable was created via a method, make sure we add a call to that method to our script
                    for (int k = 0; k < IntMethodStack.Count; k++)
                        Script.AppendLine(WrapMethodInTryCatch(IntMethodStack[k]));
                }

                // Script up all the method calls for this interface node                
                InterfaceNode oInterfaceNode = oLibraryNode.InterfaceNodes[i];
                for (int j = 0; j < oInterfaceNode.MethodNodes.Count; j++)
                {
                    XmlQualifiedName MethodQName = new XmlQualifiedName(oInterfaceNode.MethodNodes[j].Description.MethodName, InterfaceName.ToString());
                    // Check if we have already failed to get this interface
                    if (oUncallableMethods.ContainsKey(MethodQName))
                        continue;

                    List<String> MethodStack = new List<string>();
                    if (!GenerateMethod(oLibraryNode, ObjVarName, oInterfaceNode.MethodNodes[j], MethodStack, oOutputVars, oLibraryNode.Description.Interfaces.Count + 1, true))
                    {
                        oUncallableMethods.Add(MethodQName, MethodQName);
                        Log.Write(MethodBase.GetCurrentMethod(), "Could not generate method '" + oInterfaceNode.MethodNodes[j].Description.OriginalMethodName + 
                            "' for interface '" + oLibraryNode.InterfaceNodes[i].Description.Name + "'.  Skipping method", Log.LogType.Warning);
                        continue;
                    }
                    // Add all the mthod calls to the script
                    for(int k = 0; k < MethodStack.Count; k++)
                        Script.AppendLine(WrapMethodInTryCatch(MethodStack[k]));
                }
            }

            // Create the output variables
            StringBuilder OutputVariableDecl = new StringBuilder();
            foreach (String OutputVarName in oOutputVars.Values)
            {
                OutputVariableDecl.AppendLine("var " + OutputVarName + ";");
            }

            return OutputVariableDecl.ToString() + Script.ToString();
        }

        /// <summary>
        /// Generate script for a call to a method
        /// </summary>
        private bool GenerateMethod(LibraryNode oLibraryNode, String ObjVarName, MethodNode oMethodNode, List<String> MethodStack, SortedList<XmlQualifiedName, string> oOutputVars, int MaxSearchDepth, bool bShowErrors)
        {
            StringBuilder ReturnVar = new StringBuilder();
            StringBuilder Method = new StringBuilder();

            // Add the method as a function call.  If this method has In parameters, then regardless of whether it is a FUNC or GET,
            // the output script will look like a FUNC.
            if ((oMethodNode.Description.InvokeKind == eInvokeKind.INVOKE_FUNC) ||
                ((oMethodNode.Description.InvokeKind == eInvokeKind.INVOKE_PROPERTYGET) && (oMethodNode.Description.InParameterCount > 0)))
            {
                Method.Append(ObjVarName + ".");
                Method.Append(oMethodNode.Description.OriginalMethodName);
                Method.Append("(");
                // Add the parameters
                for (int i = 0; i < oMethodNode.ParameterNodes.Count; i++)
                {
                    if (i != 0)
                        Method.Append(", ");

                    String Param = GenerateParameter(oLibraryNode, oMethodNode.ParameterNodes[i], MethodStack, oOutputVars, MaxSearchDepth, false);
                    if (String.IsNullOrEmpty(Param))
                    {
                        if(bShowErrors)
                            Log.Write(MethodBase.GetCurrentMethod(), "Could not generate parameter '" + oMethodNode.ParameterNodes[i].Description.Name + 
                                "' for the function '" + oMethodNode.Description.OriginalMethodName + "'", Log.LogType.Warning);
                        return false;
                    }
                    Method.Append(Param);
                }
                Method.Append(");");
            }
            // Add the method as a get
            else if (oMethodNode.Description.InvokeKind == eInvokeKind.INVOKE_PROPERTYGET)
            {
                Method.Append(ObjVarName + ".");
                Method.Append(oMethodNode.Description.OriginalMethodName);
                Method.Append(";");
            }
            // Add the method as put
            else if (oMethodNode.Description.InvokeKind == eInvokeKind.INVOKE_PROPERTYPUT)
            {
                Method.Append(ObjVarName + ".");
                Method.Append(oMethodNode.Description.OriginalMethodName);
                String Param = "";
                // Typically PUT methods take one parameter
                if (oMethodNode.ParameterNodes.Count == 1)
                {
                    Method.Append(" = ");
                    Param = GenerateParameter(oLibraryNode, oMethodNode.ParameterNodes[0], MethodStack, oOutputVars, MaxSearchDepth, false);
                }
                // It is possible for them to take more than one though, but ensure the final parameter is the "value" parameter
                else if ((oMethodNode.ParameterNodes.Count > 1) && (oMethodNode.ParameterNodes[oMethodNode.ParameterNodes.Count - 1].Description.ParamSchemaElement.Name.Equals("value", StringComparison.CurrentCultureIgnoreCase)))
                {
                    Method.Append("(");
                    // Add the parameters
                    for (int i = 0; i < oMethodNode.ParameterNodes.Count - 1; i++)
                    {
                        if (i != 0)
                            Method.Append(", ");

                        String PutParam = GenerateParameter(oLibraryNode, oMethodNode.ParameterNodes[i], MethodStack, oOutputVars, MaxSearchDepth, false);
                        if (String.IsNullOrEmpty(PutParam))
                        {
                            if(bShowErrors)
                                Log.Write(MethodBase.GetCurrentMethod(), "Could not generate parameter '" + oMethodNode.ParameterNodes[i].Description.Name +
                                    "' for the function '" + oMethodNode.Description.OriginalMethodName + "'", Log.LogType.Warning);
                            return false;
                        }
                        Method.Append(PutParam);
                    }
                    Method.Append(")");
                    // Add the parameter that we are assigning the PUT to
                    Method.Append(" = ");
                    Param = GenerateParameter(oLibraryNode, oMethodNode.ParameterNodes[oMethodNode.ParameterNodes.Count - 1], MethodStack, oOutputVars, MaxSearchDepth, false);
                }
                else
                {
                    Log.Write(MethodBase.GetCurrentMethod(), "The put property '" + oMethodNode.Description.OriginalMethodName + "' either has no parameters or it's last one does equal 'value'", Log.LogType.Warning);
                    return false;
                }

                if (String.IsNullOrEmpty(Param))
                {
                    if(bShowErrors)
                        Log.Write(MethodBase.GetCurrentMethod(), "Could not generate a parameter for the function '" + oMethodNode.Description.OriginalMethodName + "'", Log.LogType.Warning);
                    return false;
                }
                Method.Append(Param);
                Method.Append(";");
            }

            // Set the return variable
            if (oMethodNode.Description.ReturnDesc != null)
            {
                String ReturnVarname = null;
                if (oOutputVars.ContainsKey(oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName))
                    ReturnVarname = oOutputVars[oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName];
                else
                {
                    ReturnVarname = "ret" + oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName.Name;
                    oOutputVars.Add(oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName, ReturnVarname);
                }
                ReturnVar.Append(ReturnVarname);
                ReturnVar.Append(" = ");
            }

            MethodStack.Add(ReturnVar.ToString() + Method.ToString());
            return true;
        }

        /// <summary>
        /// Generate script for a parameter of a method
        /// </summary>
        private String GenerateParameter(LibraryNode oLibraryNode, ParameterNode oParameterNode, List<String> MethodStack, SortedList<XmlQualifiedName, string> oOutputVars, int MaxSearchDepth, bool bShowErrors)
        {
            StringBuilder Param = new StringBuilder();

            // If the parameter is an input parameter then try and use a direct value and not a variable
            if (oParameterNode.Description.ParamDirection == eParamDirection.In)
            {
                XmlSchemaType oSchemaType = XMLHelper.GetTypeFromSchema(oParameterNode.Description.ParamSchemaElement.SchemaTypeName, oCOMInputHandler.SchemaSet);

                // Special case the variant type
                if (oParameterNode.Description.ParamSchemaElement.SchemaTypeName.Name.Equals("variant"))
                {
                    int ColonIndex = oParameterNode.Value.LastIndexOf(":");
                    XmlQualifiedName oQTypeName = new XmlQualifiedName(oParameterNode.Value);
                    // If the oParameterNode.Value contains a ':' then split its QName into nameapace and name
                    if (-1 != ColonIndex)
                        oQTypeName = new XmlQualifiedName(oParameterNode.Value.Substring(ColonIndex + 1), oParameterNode.Value.Substring(0, ColonIndex));
                    String ParamName = GetVariableForType(oLibraryNode, oQTypeName, MethodStack, oOutputVars, MaxSearchDepth, false);
                    if (String.IsNullOrEmpty(ParamName))
                    {
                        // Try to just get any variant type for the parameter
                        ParamName = GetVariableForType(oLibraryNode, oParameterNode.Description.ParamSchemaElement.SchemaTypeName, MethodStack, oOutputVars, MaxSearchDepth, false);
                        if (String.IsNullOrEmpty(ParamName) && bShowErrors)
                        {
                            //Log.Write(MethodBase.GetCurrentMethod(), "Cannot add parameter of type '" + oQTypeName.ToString() + "' for a variant parameter", Log.LogType.Warning);
                            return null;
                        }
                    }
                    Param.Append(ParamName);
                }
                else if (oSchemaType is XmlSchemaSimpleType)
                {
                    String typename = oParameterNode.Description.ParamSchemaElement.SchemaTypeName.Name;
                    // Check if it is an enumeration, because we can add those as well
                    XmlSchemaEnumerationFacet oEnum = FacetRestrictions.HasEnumerationFacet(oSchemaType as XmlSchemaSimpleType);
                    if (null != oEnum)
                        typename = (oEnum.Parent as XmlSchemaSimpleTypeRestriction).BaseTypeName.Name;

                    switch (typename)
                    {
                        case "string":
                            // Add the value in quotes and escaped
                            Param.Append("\"");
                            Param.Append(EscapeString(oParameterNode.Value));
                            Param.Append("\"");
                            break;
                        case "short":
                        case "int":
                        case "float":
                        case "double":
                        case "boolean":
                        case "decimal":
                        case "byte":
                        case "unsignedByte":
                        case "unsignedShort":
                        case "unsignedInt":
                        case "long":
                        case "unsignedLong":
                        case "integer":
                        case "nonNegativeInteger":
                            // Add the value without quotes
                            Param.Append(oParameterNode.Value);
                            break;
                        default:
                            //if(bShowErrors)
                            //    Log.Write(MethodBase.GetCurrentMethod(), "Cannot add parameter of type '" + oParameterNode.Description.ParamSchemaElement.SchemaTypeName.Name + "'", Log.LogType.Warning);
                            return null;
                    }
                }
                else if (oSchemaType is XmlSchemaComplexType)
                {
                    String ParamName = GetVariableForType(oLibraryNode, oParameterNode.Description.ParamSchemaElement.SchemaTypeName,
                        MethodStack, oOutputVars, MaxSearchDepth, false);
                    if (String.IsNullOrEmpty(ParamName))
                    {
                        //Log.Write(MethodBase.GetCurrentMethod(), "Cannot add parameter of type '" + oParameterNode.Description.ParamSchemaElement.SchemaTypeName.Name + "'", Log.LogType.Warning);
                        return null;
                    }
                    Param.Append(ParamName);
                }
                else
                    Log.Write(MethodBase.GetCurrentMethod(), "Encountered unknown schema type", Log.LogType.Error);
            }
            // If the param is an output variable, then create an output variable and make the param the variable name
            else if (oParameterNode.Description.ParamDirection == eParamDirection.Out)
            {
                XmlQualifiedName ParamType = oParameterNode.Description.ParamSchemaElement.SchemaTypeName;
                // If an output parameter of the correct type already exists, use that
                if (oOutputVars.ContainsKey(ParamType))
                    Param.Append(oOutputVars[ParamType]);
                else
                {
                    // Create a new output parameter
                    String OuputVarName = "out" + oParameterNode.Description.ParamSchemaElement.SchemaTypeName.Name;
                    Param.Append(OuputVarName);
                    oOutputVars.Add(ParamType, OuputVarName);
                }
            }
            else
            {
                Log.Write(MethodBase.GetCurrentMethod(), "The parameter was neither in nor out", Log.LogType.Error);
            }

            return Param.ToString();
        }

        /// <summary>
        /// Tries to generate a variable for the type passed in.  The name of the variable is returned, and if it required a method to be called
        /// and an output variable to be created then MethodStack and oOutputVars are adjusted accordingly
        /// </summary>
        private string GetVariableForType(LibraryNode oLibraryNode, XmlQualifiedName QTypeName, List<String> MethodStack, SortedList<XmlQualifiedName, string> oOutputVars, int MaxSearchDepth, bool bShowErrors)
        {
            // Implement a max search depth parameter.  It is passed in, and decremented.  This way we search breadth (i.e. all the
            // nodes) before depth (i.e. find a candidate and try to make it work)
            if (MaxSearchDepth <= 0)
                return null;
            MaxSearchDepth = MaxSearchDepth - 1;

            // See if we have an output variable with this type already
            if (oOutputVars.ContainsKey(QTypeName))
                return oOutputVars[QTypeName];

            int SearchDepth = MaxSearchDepth;
            for (int i = 0; i <= SearchDepth; i++)
            {
                // Try to get an interface variable
                String TypeVar = null;
                // Only try to get interface variables for types in the Library namespace
                if (QTypeName.Namespace.Equals(oLibraryNode.Description.Namespace))
                    TypeVar = GetInterfaceVariable(oLibraryNode, QTypeName, MethodStack, oOutputVars, i, bShowErrors);

                if (!String.IsNullOrEmpty(TypeVar))
                {
                    return TypeVar;
                }
                else
                {
                    // Find methods to call to generate a variable of this type
                    TypeVar = GetVarFromMethod(oLibraryNode, QTypeName, MethodStack, oOutputVars, i, bShowErrors);
                    if (!String.IsNullOrEmpty(TypeVar))
                    {
                        return TypeVar;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Returns a variable name that can be used to access methods on a particular interface.
        /// </summary>
        private String GetInterfaceVariable(LibraryNode oLibraryNode, XmlQualifiedName InterfaceName, List<String> MethodStack, SortedList<XmlQualifiedName, string> oOutputVars, int MaxSearchDepth, bool bShowErrors)
        {
            if (null == oInterfaceVariables)
                oInterfaceVariables = new SortedList<XmlQualifiedName, string>(new XMLHelper.XmlQualifiedNameComparer());

            // See if we have already discovered this interface variable
            if (oInterfaceVariables.ContainsKey(InterfaceName))
                return oInterfaceVariables[InterfaceName];

            // See if we have already tried and failed to generate this interface variable
            if (oNEInterfaceVariables.ContainsKey(InterfaceName))
                return null;

            // If the interface is the first in the LibraryDescription then it is our target interface and we use 
            // constObj
            if (oLibraryNode.Description.Interfaces[0].Name.Equals(InterfaceName.Name) &&
                oLibraryNode.Description.Interfaces[0].Namespace.Equals(InterfaceName.Namespace))
            {
                oInterfaceVariables.Add(InterfaceName, constOBJ);
                return constOBJ;
            }

            // Now we go through each of our interfaces in order to find one that has a 'get_' method that returns
            // the interface we are interested in.
            for (int i = 0; i < oLibraryNode.Description.Interfaces.Count; i++)
            {
                InterfaceDescription oInterfaceDescription = oLibraryNode.Description.Interfaces[i];
                
                // Make sure we don't search for the 'get_' method on the interface we are looking for, this leads to an infinite loop
                if (oInterfaceDescription.Name.Equals(InterfaceName.Name) && oInterfaceDescription.Namespace.Equals(InterfaceName.Namespace))
                    continue;

                for (int j = 0; j < oInterfaceDescription.Methods.Count; j++)
                {
                    MethodDescription oMethodDescription = oInterfaceDescription.Methods[j];
                    
                    if ( (oMethodDescription.InvokeKind == eInvokeKind.INVOKE_PROPERTYGET) &&
                        (oMethodDescription.ReturnDesc.ParamSchemaElement.SchemaTypeName.Name.Equals(InterfaceName.Name) &&
                         oMethodDescription.ReturnDesc.ParamSchemaElement.SchemaTypeName.Namespace.Equals(InterfaceName.Namespace) ))
                    {
                        // We got a hit, get the variable for this interface
                        String GetMethodInterfaceVar = GetVariableForType(oLibraryNode, new XmlQualifiedName(oInterfaceDescription.Name, oInterfaceDescription.Namespace), MethodStack, oOutputVars, MaxSearchDepth, bShowErrors);
                        //String GetMethodInterfaceVar = GetInterfaceVariable(oLibraryNode, new XmlQualifiedName(oInterfaceDescription.Name, oInterfaceDescription.Namespace), MethodStack, oOutputVars);
                        // If we couldn't find this interface, keep on trying with other methods
                        if (String.IsNullOrEmpty(GetMethodInterfaceVar))
                            continue;
                        else
                        {
                            String InterfaceVar = GetMethodInterfaceVar + "." + oMethodDescription.OriginalMethodName;
                            // The GET method may take parameters
                            if (oMethodDescription.InParameterCount > 0)
                            {
                                bool bFoundGetNode = false;
                                // We need to find the MethodNode that calls this GET method, so we can get an appropriate value for the
                                // input parameters
                                for (int k = 0; i < oLibraryNode.InterfaceNodes.Count; k++)
                                {
                                    // Is this the interface we are looking for
                                    if (oLibraryNode.InterfaceNodes[k].Description.Name.Equals(oInterfaceDescription.Name, StringComparison.CurrentCulture) &&
                                        oLibraryNode.InterfaceNodes[k].Description.Namespace.Equals(oInterfaceDescription.Namespace, StringComparison.CurrentCulture))
                                    {
                                        InterfaceNode oGetInterfaceNode = oLibraryNode.InterfaceNodes[k];
                                        for (int l = 0; l < oGetInterfaceNode.MethodNodes.Count; l++)
                                        {
                                            if (oGetInterfaceNode.MethodNodes[l].Description.MethodName.Equals(oMethodDescription.MethodName, StringComparison.CurrentCulture))
                                            {
                                                // Need to add the method that returns parameter to the MethodStack
                                                if (!GenerateMethod(oLibraryNode, GetMethodInterfaceVar, oGetInterfaceNode.MethodNodes[l], MethodStack, oOutputVars, MaxSearchDepth, bShowErrors))
                                                    break;  // We couldn't generate the GET method, try a different interface
                                                // Recover the output var created, there should be only one output variable of the return type
                                                if (oOutputVars.ContainsKey(InterfaceName))
                                                {
                                                    InterfaceVar = oOutputVars[InterfaceName];
                                                    bFoundGetNode = true;
                                                    break;
                                                }
                                                else
                                                    Log.Write(MethodBase.GetCurrentMethod(), "The GET return variable with type '" +
                                                        InterfaceName + "' did not exist", Log.LogType.Warning);
                                            }
                                        }
                                    }
                                    if (bFoundGetNode)
                                        break;
                                }
                                if (!bFoundGetNode)
                                    continue;
                            }
                            oInterfaceVariables.Add(InterfaceName, InterfaceVar);
                            return InterfaceVar;
                        }
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Returns a parameter variable name where the variable was set by the return value or output of another method.  Any additional
        /// methods that are needed are added to the MethodStack.
        /// </summary>
        private String GetVarFromMethod(LibraryNode oLibraryNode, XmlQualifiedName VarType, List<String> MethodStack, SortedList<XmlQualifiedName, string> oOutputVars, int MaxSearchDepth, bool bShowErrors)
        {
            // Now we go through each of our interfaces in order to find one that has a method that returns or has an out parameter of
            // the type we are interested in.
            for (int i = 0; i < oLibraryNode.InterfaceNodes.Count; i++)
            {
                InterfaceNode oInterfaceNode = oLibraryNode.InterfaceNodes[i];

                // The type we are looking for may be an interface, if so don't search on this interface as this leads to an infinite loop
                if (oInterfaceNode.Description.Name.Equals(VarType.Name) && oInterfaceNode.Description.Namespace.Equals(VarType.Namespace))
                    continue;

                for (int j = 0; j < oInterfaceNode.MethodNodes.Count; j++)
                {
                    MethodNode oMethodNode = oInterfaceNode.MethodNodes[j];

                    if ((oMethodNode.Description.InvokeKind == eInvokeKind.INVOKE_PROPERTYGET) ||
                         (oMethodNode.Description.InvokeKind == eInvokeKind.INVOKE_FUNC))
                    {
                        // Check the return value
                        if ((null != oMethodNode.Description.ReturnDesc) &&
                            (oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName.Name.Equals(VarType.Name) &&
                             oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName.Namespace.Equals(VarType.Namespace)))
                        {
                            // Get the variable for this interface
                            String InterfaceVar = GetVariableForType(oLibraryNode, new XmlQualifiedName(oInterfaceNode.Description.Name, oInterfaceNode.Description.Namespace), MethodStack, oOutputVars, MaxSearchDepth, bShowErrors);
                            // If we couldn't find this interface, continue searching
                            if (String.IsNullOrEmpty(InterfaceVar))
                                break;  // All these methods are on the same interface, so if we can't get this interface skip it entirely

                            String ParameterVar = InterfaceVar + "." + oMethodNode.Description.OriginalMethodName;
                            if (oMethodNode.Description.InvokeKind == eInvokeKind.INVOKE_PROPERTYGET)
                                return ParameterVar;
                            else
                            {
                                // Need to add the method that returns parameter to the MethodStack
                                if (!GenerateMethod(oLibraryNode, InterfaceVar, oMethodNode, MethodStack, oOutputVars, MaxSearchDepth, bShowErrors))
                                    continue;   // We couldn't create this method, but maybe we can create others
                                // There should be only one output variable of the return type
                                if (oOutputVars.ContainsKey(oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName))
                                    return oOutputVars[oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName];
                                else
                                    Log.Write(MethodBase.GetCurrentMethod(), "The return variable with type '" +
                                        oMethodNode.Description.ReturnDesc.ParamSchemaElement.SchemaTypeName +
                                        "' did not exist", Log.LogType.Warning);
                            }
                        }
                        else
                        {
                            // Check the out parameters
                            for (int k = 0; k < oMethodNode.ParameterNodes.Count; k++)
                            {
                                ParameterNode oOutParameterNode = oMethodNode.ParameterNodes[k];
                                if (oOutParameterNode.Description.ParamDirection == eParamDirection.Out)
                                {
                                    if ((oOutParameterNode.Description.ParamSchemaElement.SchemaTypeName.Name.Equals(VarType.Name) &&
                                        oOutParameterNode.Description.ParamSchemaElement.SchemaTypeName.Namespace.Equals(VarType.Namespace)))
                                    {
                                        // Get the variable for this interface
                                        String InterfaceVar = GetVariableForType(oLibraryNode, new XmlQualifiedName(oInterfaceNode.Description.Name, oInterfaceNode.Description.Namespace), MethodStack, oOutputVars, MaxSearchDepth, bShowErrors);
                                        // If we couldn't find this interface, continue searching
                                        if (String.IsNullOrEmpty(InterfaceVar))
                                            break;

                                        // Need to add the method with 'out' parameter to the MethodStack
                                        if (!GenerateMethod(oLibraryNode, InterfaceVar, oMethodNode, MethodStack, oOutputVars, MaxSearchDepth, bShowErrors))
                                            break;
                                        // There should be only one output variable of the return type
                                        if (oOutputVars.ContainsKey(oOutParameterNode.Description.ParamSchemaElement.SchemaTypeName))
                                            return oOutputVars[oOutParameterNode.Description.ParamSchemaElement.SchemaTypeName];
                                        else
                                            Log.Write(MethodBase.GetCurrentMethod(), "The output variable with type '" +
                                                oOutParameterNode.Description.ParamSchemaElement.SchemaTypeName +
                                                "' did not exist", Log.LogType.Warning);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            // We did not find it
            return null;
        }

        private String WrapMethodInTryCatch(String Method)
        {
            StringBuilder Wrapper = new StringBuilder();
            Wrapper.Append(constOutputFn + "(" + constFormatOpFn + "(\"");
            Wrapper.Append(EscapeString(Method));
            Wrapper.AppendLine("\"));");
            Wrapper.Append("try {");
            Wrapper.Append(Method);
            Wrapper.Append("} catch(e){ " + constOutputFn + "(" + constFormatErrorFn + "(e)); } " + constOutputLnFn + "();");
            return Wrapper.ToString();
        }

        ///// <summary>
        ///// Creates a share to the specified path with the specified name
        ///// </summary>
        //private void CreateShare(String ShareName, String SharePath)
        //{
        //    // Create a share for the output folder that will contain the test case.  This way we can ShellExecute the HTML.
        //    Process oNetShare = new Process();
        //    oNetShare.StartInfo.UseShellExecute = true;
        //    oNetShare.StartInfo.CreateNoWindow = true;
        //    //oNetShare.StartInfo.FileName = Environment.ExpandEnvironmentVariables(@"%HOMEDRIVE%\windows\system32\net.exe");
        //    oNetShare.StartInfo.FileName = "net";
        //    oNetShare.StartInfo.Arguments = "share " + ShareName + "=" + Path.GetFullPath(SharePath);
        //    Log.Write(MethodBase.GetCurrentMethod(), "Creating share using '" + oNetShare.StartInfo.FileName + " " + oNetShare.StartInfo.Arguments + "'", Log.LogType.Info);
        //    oNetShare.Start();
        //    Thread.Sleep(500);
        //    oNetShare.Close();
        //}

        ///// <summary>
        ///// Deletes a share with the specified name
        ///// </summary>
        //private void DeleteShare(String ShareName)
        //{
        //    // Delete a share for the output folder that will contain the test case.  This way we can ShellExecute the HTML.
        //    Process oNetShare = new Process();
        //    oNetShare.StartInfo.UseShellExecute = true;
        //    oNetShare.StartInfo.CreateNoWindow = true;
        //    oNetShare.StartInfo.FileName = "net";
        //    oNetShare.StartInfo.Arguments = "share " + ShareName + " /delete";
        //    Log.Write(MethodBase.GetCurrentMethod(), "Deleting share using '" + oNetShare.StartInfo.FileName + " " + oNetShare.StartInfo.Arguments + "'", Log.LogType.Info);
        //    oNetShare.Start();
        //    Thread.Sleep(500);
        //    oNetShare.Close();
        //}

        /// <summary>
        /// Returns a string with '\' and '"' escaped
        /// </summary>
        private String EscapeString(String Input)
        {
            String Output = Input.Replace(@"\", @"\\");
            Output = Output.Replace("\"", "\\\"");
            return Output;
        }
    }
}
